﻿
Imports System.Threading
Imports locks

Namespace locks
    Public Interface ilock
        Sub enter()
        Sub [exit]()
        Function thread_owned() As Boolean
    End Interface

    Public Module __lock
        Public ReadOnly trace As Boolean = False

        Sub New()
            trace = envs.perftrace() OrElse
                    envs.env_value(New String() {"lock_trace",
                                                 "LOCK_TRACE",
                                                 "locktrace",
                                                 "LOCKTRACE"},
                                   Nothing)
        End Sub
    End Module
End Namespace

Public Structure lock(Of T As {Structure, ilock})
    Private Shared ReadOnly thread_owned As Boolean
    Private ownerTID As Int32
    Private l As T

    Shared Sub New()
        assert(INVALID_THREAD_ID = [default](Of Int32)())
        Dim l As T
        thread_owned = l.thread_owned()
    End Sub

    Public Function held_in_thread() As Boolean
        Return ownerTID = currentThreadID()
    End Function

    Public Function held() As Boolean
        Return ownerTID <> INVALID_THREAD_ID
    End Function

    Private Sub debug_wait()
        Dim n As Int64 = 0
        n = Now.milliseconds()
        l.enter()
        If Now.milliseconds() - n > spinwait.halfTimesliceLengthMs AndAlso
           (errorHandle.skipErrorType Is Nothing OrElse
            Not errorHandle.skipErrorType.Contains(errorHandle.errorTypeChar(errorHandle.errorType.performance))) Then
            Console.WriteLine(patchupErrMsg(strcat(callstack(),
                                                   " is using ",
                                                   Now.milliseconds() - n,
                                                   "ms to wait for another thread to finish"),
                                            errorHandle.errorType.performance))
        End If
    End Sub

    Public Sub wait()
        Try
            If __lock.trace Then
                debug_wait()
            Else
                l.enter()
            End If
        Catch ex As ThreadAbortException
            Console.WriteLine(patchupErrMsg("thread abort.", errorHandle.errorType.warning))
        End Try
        If thread_owned Then
            assert(ownerTID = INVALID_THREAD_ID)
            ownerTID = currentThreadID()
        End If
    End Sub

    Public Sub release()
        If thread_owned Then
            assert(ownerTID = currentThreadID())
            ownerTID = INVALID_THREAD_ID
        End If
        l.[exit]()
    End Sub

    Public Sub locked(ByVal d As void)
        If held_in_thread() Then
            void_(d)
        Else
            wait()
            void_(d)
            release()
        End If
    End Sub

    Public Function locked(Of OT)(ByVal d As _do(Of OT),
                                  Optional ByVal falseValue As OT = Nothing) As OT
        Dim rtn As OT = Nothing
        locked(Sub()
                   rtn = do_(d, falseValue)
               End Sub)
        Return rtn
    End Function
End Structure

Public Module _lock
    Public Sub assert_in_lock(Of T As {ilock, Structure})(ByRef l As lock(Of T))
        assert(l.held_in_thread())
    End Sub
End Module
